pi[i] = 字串 s[0..i] 的最長真前綴,且同時是其後綴的長度。
例:s = "ababaca"
pi = [0,0,1,2,3,0,1](常見教科書版本;也有以 1-based 表示者)
j = pi[i-1]
while (j > 0 && s[i] != s[j]) j = pi[j-1];
if (s[i] == s[j]) j++;
pi[i] = j;
直觀理解:嘗試延伸目前匹配長度 j,失敗就「回退」到更短的 border 長度 pi[j-1] 再試。
同時是前綴與後綴的子字串。所有 border 長度可由 pi[n-1] 一路沿 pi[len-1] 追溯。
原題:
https://cses.fi/problemset/task/1753
題意:
給字串 T(text)與 P(pattern),求 P 在 T 中出現的次數(允許重疊)。
解題思路:
#include <bits/stdc++.h>
using namespace std;
vector<int> prefix_function(const string& s){
    int n = (int)s.size();
    vector<int> pi(n);
    for(int i=1;i<n;i++){
        int j = pi[i-1];
        while(j>0 && s[i]!=s[j]) j = pi[j-1];
        if(s[i]==s[j]) j++;
        pi[i]=j;
    }
    return pi;
}
int main(){
    ios::sync_with_stdio(false); cin.tie(nullptr);
    string T, P; 
    if(!(cin >> T >> P)) return 0;
    auto pi = prefix_function(P);
    int j = 0, ans = 0;
    for(char c: T){
        while(j>0 && c!=P[j]) j = pi[j-1];
        if(c==P[j]) j++;
        if(j==(int)P.size()){
            ans++;
            j = pi[j-1]; // 允許重疊
        }
    }
    cout << ans << "\n";
    return 0;
}
原題:
https://cses.fi/problemset/task/1732
題意:
給字串 s,輸出所有 border 長度(即同時為 s 的前綴與後綴的非空子字串長度),由小到大。若沒有,輸出空行。
解題思路:
#include <bits/stdc++.h>
using namespace std;
vector<int> prefix_function(const string& s){
    int n=(int)s.size(); vector<int> pi(n);
    for(int i=1;i<n;i++){
        int j=pi[i-1];
        while(j>0 && s[i]!=s[j]) j=pi[j-1];
        if(s[i]==s[j]) j++;
        pi[i]=j;
    }
    return pi;
}
int main(){
    ios::sync_with_stdio(false); cin.tie(nullptr);
    string s; if(!(cin>>s)) return 0;
    auto pi = prefix_function(s);
    vector<int> lens;
    for(int len = pi.back(); len > 0; len = pi[len-1]) lens.push_back(len);
    sort(lens.begin(), lens.end());
    for(size_t i=0;i<lens.size();i++){
        if(i) cout << ' ';
        cout << lens[i];
    }
    cout << "\n";
    return 0;
}
原題:
https://leetcode.com/problems/find-the-index-of-the-first-occurrence-in-a-string/description/
題意:
回傳 needle 在 haystack 中第一次出現的起始索引;若不存在回傳 -1。空字串按題意通常回傳 0。
解題思路:
經典 KMP:對 needle 建 π,掃 haystack 尋找第一個 j == |needle| 的位置即可。
class Solution {
    vector<int> prefix_function(const string& s){
        int n=s.size(); vector<int> pi(n);
        for(int i=1;i<n;i++){
            int j=pi[i-1];
            while(j>0 && s[i]!=s[j]) j=pi[j-1];
            if(s[i]==s[j]) j++;
            pi[i]=j;
        }
        return pi;
    }
public:
    int strStr(string haystack, string needle) {
        if(needle.empty()) return 0;
        auto pi = prefix_function(needle);
        int j=0;
        for(int i=0;i<(int)haystack.size();i++){
            while(j>0 && haystack[i]!=needle[j]) j=pi[j-1];
            if(haystack[i]==needle[j]) j++;
            if(j==(int)needle.size()){
                return i - j + 1;
            }
        }
        return -1;
    }
};
原題:
https://leetcode.com/problems/longest-happy-prefix/
題意:
給字串 s,找出最長的非空字串,使它同時是 s 的前綴與後綴(不可等於整個字串),回傳該字串。
解題思路:
class Solution {
    vector<int> prefix_function(const string& s){
        int n=s.size(); vector<int> pi(n);
        for(int i=1;i<n;i++){
            int j=pi[i-1];
            while(j>0 && s[i]!=s[j]) j=pi[j-1];
            if(s[i]==s[j]) j++;
            pi[i]=j;
        }
        return pi;
    }
public:
    string longestPrefix(string s) {
        auto pi = prefix_function(s);
        int len = pi.back();
        return s.substr(0, len);
    }
};